package client;

import common.*;


import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.io.*;



/**
 */
public abstract class GameClient extends Thread{

    /** address of server */
    protected InetAddress serverAddress;
    
    /** puerto de conexion a el servidor*/
    protected  int   port;
    
    /** connection to server */
    protected SocketChannel channel;

    /** queue for incoming events */
    protected EventQueue inQueue;
    /** queue for outoging events */
    protected EventQueue outQueue;
    
    /** reference to NIOEventReader that reads events from the server */
    protected NIOEventReader netReader;

    /** buffer for outgoing events */
    protected ByteBuffer writeBuffer;

    /** id of our player */
    protected String playerId;
    /** id of our current opponent */
    protected String opponentId;

    /** estamos jugando? */
    protected boolean inGame = false;

    /** aun en marcha? */
    protected boolean running = true;

    /** 
     * do some initialization
     * args[0] --> ip
     * args[1] --> nombre (playerId)
     * args[2] --> puerto
     */
    public void init(String args[]) {	
	inQueue = new EventQueue("GameClient-in");
	outQueue = new EventQueue("GameClient-out");
	writeBuffer = ByteBuffer.allocate(Globals.MAX_EVENT_SIZE );

	try {
	    serverAddress = InetAddress.getByName(args[0]);
	}
	catch (UnknownHostException uhe) {
	    System.out.println("host desconocido: " + args[0]);
	    System.exit(1);
	}
	
	try {
	    this.port = Integer.parseInt(args[2]);//puerto
	}
	catch (Exception e) {
	    System.out.println("puerto desconocido: " + args[2]);
	    System.exit(1);
	}
	this.playerId = args[1];//identificador del jugador
	
	// connect to the server
	if (!connect()) 
	    System.exit(1);
	
	// start our net reader
	netReader = new NIOEventReader(this, channel, inQueue);
	netReader.start();
	
    }

    public void run() {
	// now login
	login();

	// wait for LOGIN_ACK (hack)
	threadSleep(200L);

	// main loop
	while(running) {
	    processIncomingEvents();
	    writeOutgoingEvents();

	    // take a quick nap so we don't spin too hard 
	    //when nothing is happening
	    threadSleep(50);
	}
    }


    /** 
     * subclasses must implement to provide their GameName 
     */
    public abstract String getGameName();

    /**
     * subclasses must implement this factory method 
     */
    public abstract GameEvent createGameEvent();

    /**
     * and this one to create a game specific login event
     */
    public abstract GameEvent createLoginEvent();
    
    /**
     * and this one to create a game specific disconnect event
     */
    public abstract GameEvent createDisconnectEvent(String reason);
    
    /**
     * handle incoming GameEvents from the EventQueue
     */
    protected abstract void processIncomingEvents();

    /** 
     * write all events waiting in the outQueue
     */
    private void writeOutgoingEvents() {
	GameEvent outEvent;
	while (outQueue.size() > 0) {
	    try {
		outEvent = outQueue.deQueue();
		writeEvent(outEvent);
	    }
	    catch (InterruptedException ie) {}
	}	
    }
    
    /**
     * conectar al servidor
     */
    protected boolean connect() {
	    System.out.println("Conectando al server ....");
	try {
	    // open the socket channel
	    channel = SocketChannel.open(new InetSocketAddress(serverAddress, port));
	    channel.configureBlocking(false);

 	    // we don't like Nagle's algorithm
	    channel.socket().setTcpNoDelay(true);
	    return true;
	}
	catch (ConnectException ce) {
		System.out.println("Connect Exception: " + ce.getMessage());	    
	    return false;
	}
	catch (Exception e) {
		System.out.println("Exception while connecting"+ e);	
	    return false;
	}
    }

    /**
     * send the login event
     */
    protected void login() {
	GameEvent e = createLoginEvent();
	e.setGameName("Bowling NET");
	e.setPlayerId(playerId);
	writeEvent(e);
    }
    
    /**
     * Metodo encargado de registrar el jugador ante el servidor
     * y reservar el turno.
     * @param nick
     * @param imagen
     */
    public void jugar (String nick, String imagen)
    {    	
    	GameEvent event = new GameEventDefault(GameEventDefault.C_JUGAR_GAME,"iniciando juego",imagen,nick);
    	//outQueue = new EventQueue("GameClient-out");
    	outQueue.enQueue(event);
    	inGame=true;
    }
    
    /**
     * Metodo encargado de cambiar el turno 
     * y reservar el turno.
     * @param nick
     * @param imagen
     */
    public void cambiarTurno(int puntaje)
    {    	
    	GameEvent event = new GameEventDefault(GameEventDefault.C_CAMBIO_TURNO, puntaje);
    	outQueue.enQueue(event);
    }
    
    /**
     * Metodo encargado de desloguear el cliente.
     */
    public void salir ()
    {
    	GameEvent event = new GameEventDefault(GameEventDefault.C_LOGOUT, "Bye Bye!");
    	outQueue.enQueue(event);
    }
    
    /**
     * Metodo encargado de realizar el lanzamiento
     * @param nX
     * @param nAngulo
     * @param nVelocidad
     */
    public void lanzar (int nX,double nAngulo,int nVelocidad)
    {
    	GameEvent event = new GameEventDefault(GameEventDefault.C_LANZAR,nX,nAngulo,nVelocidad);
    	outQueue.enQueue(event);
    }
    
    /**
     * Metodo encargado de consultar los records 
     * en la BD
     * @param e
     */
    public   void consultRecords ()
    {
    	System.out.print("entro a consultar datos records cliene");
    	GameEvent e = new GameEventDefault(GameEventDefault.C_CONSULTA_RECORDS);
    	outQueue.enQueue(e);
    }
    
    

    /**
     * shutdown the client
     * stop our readers and close the channel
     */
    protected void shutdown() {
	running = false;
	netReader.shutdown();
	//	consoleReader.shutdown();
	try {
	    channel.close();
	}
	catch (IOException ioe) {
		System.out.println("exception while closing channel"+ ioe );
	}
    }    

    /** 
     * send an event to the server 
     */
    protected void writeEvent(GameEvent ge) {
	// set the gamename and player id
	ge.setGameName(getGameName());
	ge.setPlayerId(playerId);

	NIOUtils.prepBuffer(ge, writeBuffer);
	NIOUtils.channelWrite(channel, writeBuffer);
    }

    /** 
     * utility method to call Thread.sleep()
     */
    private void threadSleep(long time) {
	try { 
	    Thread.sleep(time); 
	} 
	catch(InterruptedException e) {}
    }

    public void stdOut(String str) {
	if ((str != null) && !str.equals(""))
	    System.out.println("\n" + str);
	if (inGame)
	    System.out.print( playerId + " vs. " + opponentId + " > " );
	else
	    System.out.print( playerId + " > " );

    }   
    
    public void stdErr(String str) {
	System.err.println("\n" + str);
    }
    
    public void setOpponent(String opp) {
	opponentId = opp;
    }
    
    public boolean testConect(String server,int port)
    {
    	try 
    	{
    		SocketChannel.open(new InetSocketAddress(server, port));
    		return true;
		} catch (Exception e) 
		{
			System.out.println(" No se pudo conectar al servidor "+server);
			return false;
		}
    	//SocketChannel channelTmp = SocketChannel.open(new InetSocketAddress(serverAddress, Globals.PORT));
    	
    }
    
}    

